From 9ddf9c5da61a9b196f4176855c8a446f6064b3ab Mon Sep 17 00:00:00 2001 From: robertl Date: Sun, 7 Mar 2010 04:23:42 +0000 Subject: [PATCH] Track/course upload for Garmin fitness. From Martin Buck. (His "Patch #4") --- jeeps/gps.h | 51 ++ jeeps/gpsapp.c | 1391 +++++++++++++++++++++++++++++++++++++++++++----- jeeps/gpsapp.h | 49 +- jeeps/gpscom.c | 461 +++++++++++++++- jeeps/gpscom.h | 6 +- jeeps/gpsmem.c | 120 +++++ jeeps/gpsmem.h | 30 +- 7 files changed, 1929 insertions(+), 179 deletions(-) diff --git a/jeeps/gps.h b/jeeps/gps.h index 54c492f96..92d932a7b 100644 --- a/jeeps/gps.h +++ b/jeeps/gps.h @@ -197,6 +197,57 @@ typedef struct GPS_SLap { */ } GPS_OLap, *GPS_PLap; + +typedef struct GPS_SCourse +{ + uint32 index; /* Unique among courses on device */ + char course_name[16]; /* Null-terminated unique course name */ + uint32 track_index; /* Index of the associated track + * Must be 0xFFFFFFFF if there is none*/ +} GPS_OCourse, *GPS_PCourse; + + +typedef struct GPS_SCourse_Lap +{ + uint32 course_index; /* Index of associated course */ + uint32 lap_index; /* This lap's index in the course */ + uint32 total_time; /* In hundredths of a second */ + float total_dist; /* [m] */ + double begin_lat; /* Starting position of the lap */ + double begin_lon; /* Invalid if lat,lon are 0x7FFFFFFF.*/ + double end_lat; /* Final position of the lap */ + double end_lon; /* Invalid if lat,lon are 0x7FFFFFFF.*/ + UC avg_heart_rate; /* In beats-per-minute, >0 */ + UC max_heart_rate; /* In beats-per-minute, >0 */ + UC intensity; /* 0=standard, active lap. + 1=rest lap in a workout */ + UC avg_cadence; /* In revolutions-per-minute */ +} GPS_OCourse_Lap, *GPS_PCourse_Lap; + +typedef struct GPS_SCourse_Point +{ + char name[11]; /* Null-terminated name */ + uint32 course_index; /* Index of associated course */ + time_t track_point_time; /* Time */ + UC point_type; /* generic = 0, + * summit = 1, + * valley = 2, + * water = 3, + * food = 4, + * danger = 5, + * left = 6, + * right = 7, + * straight = 8, + * first_aid = 9, + * fourth_category = 10, + * third_category = 11, + * second_category = 12, + * first_category = 13, + * hors_category = 14, + * sprint = 15 */ +} GPS_OCourse_Point, *GPS_PCourse_Point; + + typedef int (*pcb_fn) (int, struct GPS_SWay **); #include "gpsdevice.h" diff --git a/jeeps/gpsapp.c b/jeeps/gpsapp.c index fac1d6891..860c5c4e2 100644 --- a/jeeps/gpsapp.c +++ b/jeeps/gpsapp.c @@ -104,6 +104,8 @@ static void GPS_D501_Send(UC *data, GPS_PAlmanac alm); static void GPS_D550_Send(UC *data, GPS_PAlmanac alm); static void GPS_D551_Send(UC *data, GPS_PAlmanac alm); +static UC Is_Trackpoint_Invalid(GPS_PTrack *trk); + int32 gps_save_id; int gps_is_usb; @@ -3776,7 +3778,163 @@ int32 GPS_A301_Get(const char *port, GPS_PTrack **trk, pcb_fn cb) } +/* @func GPS_A302_Get ****************************************************** +** +** Get course track data from GPS with protocol A302 +** +** @param [r] port [const char *] serial port +** @param [w] trk [GPS_PTrack **] track array +** +** @return [int32] number of track entries +************************************************************************/ +/* FIXME: Merge with GPS_A301_Get()? */ +int32 GPS_A302_Get(const char *port, GPS_PTrack **trk, pcb_fn cb) +{ + static UC data[2]; + gpsdevh *fd; + GPS_PPacket tra; + GPS_PPacket rec; + int32 n; + int32 i; + + if(gps_trk_transfer == -1) + return GPS_UNSUPPORTED; + + /* Only those GPS' with L001 can send track data */ + if (!LINK_ID[gps_link_type].Pid_Course_Trk_Data) + { + GPS_Warning("Course track data unsupported"); + return GPS_UNSUPPORTED; + } + + if(!GPS_Device_On(port, &fd)) + return gps_errno; + + if(!(tra = GPS_Packet_New()) || !(rec = GPS_Packet_New())) + return MEMORY_ERROR; + + GPS_Util_Put_Short(data, + COMMAND_ID[gps_device_command].Cmnd_Transfer_Course_Tracks); + GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Command_Data, + data,2); + if(!GPS_Write_Packet(fd,tra)) + return gps_errno; + if(!GPS_Get_Ack(fd, &tra, &rec)) + return gps_errno; + if(!GPS_Packet_Read(fd, &rec)) + return gps_errno; + if(!GPS_Send_Ack(fd, &tra, &rec)) + return gps_errno; + + + n = GPS_Util_Get_Short(rec->data); + + if(n) + if(!((*trk)=(GPS_PTrack *)malloc(n*sizeof(GPS_PTrack)))) + { + GPS_Error("A302b_Get: Insufficient memory"); + return MEMORY_ERROR; + } + for(i=0;itype == LINK_ID[gps_link_type].Pid_Course_Trk_Hdr) + { + switch(gps_run_crs_trk_hdr_type) + { + case pD310: + case pD312: + GPS_D310_Get(&((*trk)[i]),rec->data); + break; + case pD311: + GPS_D311_Get(&((*trk)[i]),rec->data); + break; + default: + GPS_Error("A302b_Get: Unknown track header data"); + return PROTOCOL_ERROR; + } + (*trk)[i]->ishdr = 1; + continue; + } + + if (rec->type != LINK_ID[gps_link_type].Pid_Course_Trk_Data) + { + GPS_Error("A302b_Get: Non-Pid_Course_Trk_Data"); + return FRAMING_ERROR; + } + + (*trk)[i]->ishdr = 0; + + switch(gps_run_crs_trk_type) + { + case pD300: + GPS_D300b_Get(&((*trk)[i]),rec->data); + break; + case pD301: + GPS_D301b_Get(&((*trk)[i]),rec->data); + break; + case pD302: + GPS_D302b_Get(&((*trk)[i]),rec->data); + break; + case pD303: + case pD304: + GPS_D303b_Get(&((*trk)[i]),rec->data); + /* Fitness devices don't send track segment markers, so we have + * to create them ourselves. We do so at the beginning of the + * track or if the device signals a pause by sending two + * invalid points in a row. + */ + if (i>0) + { + if ((*trk)[i-1]->ishdr || + (Is_Trackpoint_Invalid(&((*trk)[i-1])) && + Is_Trackpoint_Invalid(&((*trk)[i])))) + { + (*trk)[i]->tnew = 1; + } + } + break; + default: + GPS_Error("A301_GET: Unknown track protocol"); + return PROTOCOL_ERROR; + } + /* Cheat and don't _really_ pass the trkpt back */ + if (cb) { + cb(n, NULL); + } + } + + if(!GPS_Packet_Read(fd, &rec)) + return gps_errno; + if(!GPS_Send_Ack(fd, &tra, &rec)) + return gps_errno; + if(rec->type != LINK_ID[gps_link_type].Pid_Xfer_Cmplt) + { + GPS_Error("A302b_Get: Error transferring course tracks"); + return FRAMING_ERROR; + } + + if(i != n) + { + GPS_Error("A302b_Get: Course track entry number mismatch"); + return FRAMING_ERROR; + } + + GPS_Packet_Del(&tra); + GPS_Packet_Del(&rec); + + if(!GPS_Device_Off(fd)) + return gps_errno; + return n; +} /* @func GPS_A300_Send ************************************************** @@ -3830,8 +3988,7 @@ int32 GPS_A300_Send(const char *port, GPS_PTrack *trk, int32 n) switch(gps_trk_type) { case pD300: - GPS_D300_Send(data,trk[i]); - len = 13; + GPS_D300_Send(data,trk[i],&len); break; default: GPS_Error("A300_Send: Unknown track protocol"); @@ -3896,11 +4053,6 @@ int32 GPS_A301_Send(const char *port, GPS_PTrack *trk, int32 n) if(gps_trk_transfer == -1) return GPS_UNSUPPORTED; - if(gps_trk_transfer == 302) { - GPS_Warning("A302: Device does not support sending tracks to it. "); - return GPS_UNSUPPORTED; - } - /* Only those GPS' with L001 can send track data */ if(!LINK_ID[gps_link_type].Pid_Trk_Data) { @@ -3953,20 +4105,19 @@ int32 GPS_A301_Send(const char *port, GPS_PTrack *trk, int32 n) switch(gps_trk_type) { case pD300: - GPS_D300_Send(data,trk[i]); - len = 13; + GPS_D300_Send(data,trk[i],&len); break; case pD301: - GPS_D301_Send(data,trk[i], 301); - len = 21; + GPS_D301_Send(data,trk[i],&len,301); break; case pD302: - GPS_D301_Send(data,trk[i], 302); - len = 25; + GPS_D301_Send(data,trk[i],&len,302); + break; + case pD303: + GPS_D303_Send(data,trk[i],&len,303); break; case pD304: - GPS_D304_Send(data,trk[i]); - len = 23; + GPS_D303_Send(data,trk[i],&len,304); break; default: GPS_Error("A301_Send: Unknown track protocol"); @@ -4009,6 +4160,135 @@ int32 GPS_A301_Send(const char *port, GPS_PTrack *trk, int32 n) } +/* @func GPS_A302_Send ************************************************** +** +** Send Course-track log to GPS +** +** Note that different to other GPS_Axxx_Send functions, the device +** communication is not initialized/ended within the function, since +** this packet transfer is only part of a series of transfers to the +** device. Communication init/end has to be handled by the caller. +** +** @param [r] port [const char *] serial port +** @param [r] trk [GPS_PTrack *] track array +** @param [r] n [int32] number of track entries +** @param [r] fd [gpsdevh *] pointer to the communication port +** +** @return [int32] success +************************************************************************/ +/* FIXME: Merge with GPS_A301_Send()? */ +int32 GPS_A302_Send(const char *port, + GPS_PTrack *trk, + int32 n, + gpsdevh *fd) +{ + UC data[GPS_ARB_LEN]; + GPS_PPacket tra; + GPS_PPacket rec; + int32 i; + int32 len; + US method; + + if(gps_trk_transfer == -1) + return GPS_UNSUPPORTED; + + /* Only those GPS' with L001 can send track data */ + if(!LINK_ID[gps_link_type].Pid_Course_Trk_Data) + { + GPS_Warning("A302b: course-track protocol unsupported"); + return GPS_UNSUPPORTED; + } + + if(!(tra = GPS_Packet_New()) || !(rec = GPS_Packet_New())) + return MEMORY_ERROR; + + GPS_Util_Put_Short(data,(US) n); + GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Records, + data,2); + if(!GPS_Write_Packet(fd,tra)) + return gps_errno; + if(!GPS_Get_Ack(fd, &tra, &rec)) + { + GPS_Error("A302_Send: Course-track start data not acknowledged"); + return FRAMING_ERROR; + } + + for(i=0;iishdr) + { + method = LINK_ID[gps_link_type].Pid_Course_Trk_Hdr; + + switch(gps_run_crs_trk_hdr_type) + { + case pD310: + case pD312: + GPS_D310_Send(data,trk[i],&len); + break; + case pD311: + GPS_D311_Send(data,trk[i],&len); + break; + default: + GPS_Error("A302_Send: Unknown track header type"); + return PROTOCOL_ERROR; + } + } + else + { + method = LINK_ID[gps_link_type].Pid_Course_Trk_Data; + + switch(gps_run_crs_trk_type) + { + case pD300: + GPS_D300_Send(data,trk[i],&len); + break; + case pD301: + GPS_D301_Send(data,trk[i],&len, 301); + break; + case pD302: + GPS_D301_Send(data,trk[i],&len, 302); + break; + case pD303: + GPS_D303_Send(data,trk[i],&len,303); + break; + case pD304: + GPS_D303_Send(data,trk[i],&len,304); + break; + default: + GPS_Error("A302_Send: Unknown track protocol"); + return PROTOCOL_ERROR; + } + } + + GPS_Make_Packet(&tra, method, data,(US) len); + + if(!GPS_Write_Packet(fd,tra)) + return gps_errno; + + if(!GPS_Get_Ack(fd, &tra, &rec)) + { + GPS_Error("A302_Send: Course-track packet not acknowledged"); + return FRAMING_ERROR; + } + } + + GPS_Util_Put_Short(data,COMMAND_ID[gps_device_command].Cmnd_Transfer_Course_Tracks); + GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Xfer_Cmplt, + data,2); + if(!GPS_Write_Packet(fd,tra)) + return gps_errno; + if(!GPS_Get_Ack(fd, &tra, &rec)) + { + GPS_Error("A302_Send: Course-track complete data not acknowledged"); + return FRAMING_ERROR; + } + + GPS_Packet_Del(&tra); + GPS_Packet_Del(&rec); + + return 1; +} + /* @func GPS_D300_Get ****************************************************** ** @@ -4108,9 +4388,11 @@ void GPS_D301b_Get(GPS_PTrack *trk, UC *data) (*trk)->Time = GPS_Math_Gtime_To_Utime((time_t)t); p+=sizeof(uint32); + /* FIXME: check validity */ (*trk)->alt = GPS_Util_Get_Float(p); p+=sizeof(float); + /* FIXME: check validity */ (*trk)->dpth = GPS_Util_Get_Float(p); p+=sizeof(float); @@ -4149,9 +4431,11 @@ void GPS_D302b_Get(GPS_PTrack *trk, UC *data) (*trk)->Time = GPS_Math_Gtime_To_Utime((time_t)t); p+=sizeof(uint32); + /* FIXME: check validity */ (*trk)->alt = GPS_Util_Get_Float(p); p+=sizeof(float); + /* FIXME: check validity */ (*trk)->dpth = GPS_Util_Get_Float(p); p+=sizeof(float); @@ -4231,13 +4515,14 @@ void GPS_D303b_Get(GPS_PTrack *trk, UC *data) /* When latitude and longitude are undefined, this field seems to be * a constant on my receiver (51 59 04 69) */ + /* FIXME: check validity */ (*trk)->alt = GPS_Util_Get_Float(p); if (lat_undefined || lon_undefined) (*trk)->alt = 0.0f; p+=sizeof(float); /* Heartrate is reported as 0 if there is no signal from * a heartrate monitor. - * 305 and 304 are identical until now. + * 303 and 304 are identical until now. */ switch (gps_trk_type) { case pD304: @@ -4259,12 +4544,6 @@ void GPS_D303b_Get(GPS_PTrack *trk, UC *data) break; } - /* There doesn't seem to be a trk_seg bool, or at least I've not - * observed it yet. One possibility is to start a new segment - * each time latitude and longitude are undefined? (Ie data from - * the heartrate monitor but none from the GPS. */ - (*trk)->tnew = 0; - return; } @@ -4326,16 +4605,19 @@ void GPS_D311_Get(GPS_PTrack *trk, UC *s) ** ** @param [w] data [UC *] string to write to ** @param [r] trk [GPS_PTrack] track data +** @param [w] len [int32 *] packet length ** ** @return [void] ************************************************************************/ -void GPS_D300_Send(UC *data, GPS_PTrack trk) +void GPS_D300_Send(UC *data, GPS_PTrack trk, int32 *len) { UC *p; p = data; GPS_A300_Encode(p,trk); + *len = 13; + return; } @@ -4343,14 +4625,16 @@ void GPS_D300_Send(UC *data, GPS_PTrack trk) /* @func GPS_D301_Send ************************************************** ** -** Form track data string +** Form track data string (D301 or D302, depending on type value) ** ** @param [w] data [UC *] string to write to ** @param [r] trk [GPS_PTrack] track data +** @param [w] len [int32 *] packet length +** @param [r] type [int] track point type (301 or 302) ** ** @return [void] ************************************************************************/ -void GPS_D301_Send(UC *data, GPS_PTrack trk, int type) +void GPS_D301_Send(UC *data, GPS_PTrack trk, int32 *len, int type) { UC *p; @@ -4364,16 +4648,32 @@ void GPS_D301_Send(UC *data, GPS_PTrack trk, int type) p+=sizeof(float); if (type == 302) { + /* Temperature */ GPS_Util_Put_Float(p, 1.0e25f); p+=sizeof(float); } *p = trk->tnew; + p+=sizeof(UC); + + *len = p-data; return; } -void GPS_D304_Send(UC *data, GPS_PTrack trk) + +/* @func GPS_D303_Send ************************************************** +** +** Form track data string (D303/D304) +** +** @param [w] data [UC *] string to write to +** @param [r] trk [GPS_PTrack] track data +** @param [w] len [int32 *] packet length +** @param [r] protoid [int] track point type (303 or 304) +** +** @return [void] +************************************************************************/ +void GPS_D303_Send(UC *data, GPS_PTrack trk, int32 *len, int protoid) { UC *p; @@ -4384,19 +4684,23 @@ void GPS_D304_Send(UC *data, GPS_PTrack trk) GPS_Util_Put_Float(p,trk->alt); p+=sizeof(float); - GPS_Util_Put_Float(p, 1.0e25f); - p+=sizeof(float); - - /* Not really clear if the members below makes sense to write or not */ + if (protoid == 304) { + GPS_Util_Put_Float(p, 1.0e25f); + p+=sizeof(float); + } *p = trk->heartrate; p+=sizeof(char); - *p = trk->cadence; - p+=sizeof(char); + if (protoid == 304) { + *p = trk->cadence; + p+=sizeof(char); - *p = trk->cadence > 0 ? trk->cadence : 0xff; - p+=sizeof(char); + *p = trk->cadence > 0 ? trk->cadence : 0xff; + p+=sizeof(char); + } + + *len = p-data; return; } @@ -4414,12 +4718,9 @@ void GPS_D304_Send(UC *data, GPS_PTrack trk) void GPS_D311_Send(UC *data, GPS_PTrack trk, int32 *len) { UC *p; - int identifier; p = data; -// sscanf(trk->trk_ident, "%d", &identifier); - identifier = 0x1234; - GPS_Util_Put_Short(p,identifier); + GPS_Util_Put_Short(p,strtoul(trk->trk_ident, NULL, 0)); p += 2; *len = p-data; @@ -6304,117 +6605,894 @@ void GPS_D1011b_Get(GPS_PLap *Lap, UC *p) } -/* - * It's unfortunate that these aren't constant and therefore switchable, - * but they really are runtime variable. Sigh. - */ -const char * -Get_Pkt_Type(US p, US d0, const char **xinfo) +/* @func GPS_A1006_Get ********************************************** +** +** Get Courses from GPS. According to Garmin protocol specification, this +** includes getting all course laps, course tracks and course points +** from the device. +** +** @param [r] port [const char *] serial port +** @param [w] crs [GPS_PCourse **] pointer to course array +** @param [w] lap [GPS_PCourse_Lap **] pointer to course lap array +** @param [w] trk [GPS_PTrack **] pointer to track array +** @param [w] lap [GPS_PCourse_Point **] pointer to course point array +** @param [w] n_lap [int32 **] pointer to number of lap entries +** @param [w] n_trk [int32 **] pointer to number of track entries +** @param [w] n_cpt [int32 **] pointer to number of course point entries +** +** @return [int32] number of course entries +************************************************************************/ + +int32 GPS_A1006_Get + (const char *port, + GPS_PCourse **crs, + pcb_fn cb) + { - *xinfo = NULL; -#define LT LINK_ID[gps_link_type] - if (p == LT.Pid_Ack_Byte) - return "ACK"; - if (p == LT.Pid_Command_Data) { - switch (d0) { - case 0: *xinfo = "Abort"; break; - case 1: *xinfo = "Xfer Alm"; break; - case 2: *xinfo = "Xfer Posn"; break; - case 3: *xinfo = "Xfer Prx"; break; - case 4: *xinfo = "Xfer Rte"; break; - case 5: *xinfo = "Xfer Time"; break; - case 6: *xinfo = "Xfer Trk"; break; - case 7: *xinfo = "Xfer Wpt"; break; - case 8: *xinfo = "Power Down"; break; - case 49: *xinfo = "Xfer PVT Start"; break; - case 50: *xinfo = "Xfer PVT Stop"; break; - case 92: *xinfo = "Flight Records"; break; - case 117: *xinfo = "Xfer Laps"; break; - case 121: *xinfo = "Xfer Categories"; break; - case 450: *xinfo = "Xfer Runs"; break; - case 451: *xinfo = "Xfer Workouts"; break; - case 452: *xinfo = "Xfer Wkt Occurrences"; break; - case 453: *xinfo = "Xfer User Profile "; break; - case 454: *xinfo = "Xfer Wkt Limits"; break; - case 561: *xinfo = "Xfer Courses"; break; - case 562: *xinfo = "Xfer Course Laps"; break; - case 563: *xinfo = "Xfer Course Point"; break; - case 564: *xinfo = "Xfer Course Tracks"; break; - case 565: *xinfo = "Xfer Course Limits"; break; + static UC data[2]; + gpsdevh *fd; + GPS_PPacket trapkt; + GPS_PPacket recpkt; + int32 i, n; - default: *xinfo = "Unknown"; - } - return "CMDDAT"; - } - if (p == LT.Pid_Protocol_Array) - return "PRTARR"; - if (p == LT.Pid_Product_Rqst) - return "PRDREQ"; - if (p == LT.Pid_Product_Data) - return "PRDDAT"; - if (p == LT.Pid_Ext_Product_Data) - return "PRDEDA"; + if (gps_course_transfer == -1) + return GPS_UNSUPPORTED; - if (p == LT.Pid_Xfer_Cmplt) - return "XFRCMP"; - if (p == LT.Pid_Date_Time_Data) - return "DATTIM"; - if (p == LT.Pid_Position_Data) - return "POS"; - if (p == LT.Pid_Prx_Wpt_Data) - return "WPT"; - if (p == LT.Pid_Nak_Byte) - return "NAK"; - if (p == LT.Pid_Records) - return "RECORD"; - if (p == LT.Pid_Rte_Hdr) - return "RTEHDR"; - if (p == LT.Pid_Rte_Wpt_Data) - return "RTEWPT"; - if (p == LT.Pid_Almanac_Data) - return "RALMAN"; - if (p == LT.Pid_Trk_Data) - return "TRKDAT"; - if (p == LT.Pid_Wpt_Data) - return "WPTDAT"; - if (p == LT.Pid_Pvt_Data) - return "PVTDAT"; - if (p == LT.Pid_Rte_Link_Data) - return "LNKDAT"; - if (p == LT.Pid_Trk_Hdr) - return "TRKHDR"; + if (!GPS_Device_On(port, &fd)) + return gps_errno; - if (p == LT.Pid_FlightBook_Record) - return "FLIBOO"; - if (p == LT.Pid_Lap) - return "LAPDAT"; - if (p == LT.Pid_Wpt_Cat) - return "WPTCAT"; - if (p == LT.Pid_Run) - return "RUNDAT"; - if (p == LT.Pid_Workout) - return "WKTDAT"; - if (p == LT.Pid_Workout_Occurrence) - return "WKTOCC"; - if (p == LT.Pid_Fitness_User_Profile) - return "UPROFI"; - if (p == LT.Pid_Workout_Limits) - return "WKTLIM"; - if (p == LT.Pid_Course) - return "CRSDAT"; - if (p == LT.Pid_Course_Lap) - return "CRSLAP"; - if (p == LT.Pid_Course_Point) - return "CRSPOI"; - if (p == LT.Pid_Course_Trk_Hdr) - return "CRSTHD"; - if (p == LT.Pid_Course_Trk_Data) - return "CRSTDA"; - if (p == LT.Pid_Course_Limits) - return "CRSLIM"; - if (p == LT.Pid_Trk2_Hdr) - return "TRKHD2"; - + if (!(trapkt = GPS_Packet_New() ) || !(recpkt = GPS_Packet_New())) + return MEMORY_ERROR; + + GPS_Util_Put_Short(data, + COMMAND_ID[gps_device_command].Cmnd_Transfer_Courses); + GPS_Make_Packet(&trapkt, LINK_ID[gps_link_type].Pid_Command_Data, + data,2); + if(!GPS_Write_Packet(fd,trapkt)) + return gps_errno; + if(!GPS_Get_Ack(fd, &trapkt, &recpkt)) + return gps_errno; + if(!GPS_Packet_Read(fd, &recpkt)) + return gps_errno; + if(!GPS_Send_Ack(fd, &trapkt, &recpkt)) + return gps_errno; + + n = GPS_Util_Get_Short(recpkt->data); + + + if(n) + if(!((*crs)=(GPS_PCourse *)malloc(n*sizeof(GPS_PCourse)))) + { + GPS_Error("A1006_Get: Insufficient memory"); + return MEMORY_ERROR; + } + + for(i=0;idata); + break; + default: + GPS_Error("A1006_Get: Unknown Course protocol %d\n", + gps_course_type); + return PROTOCOL_ERROR; + } + + // Cheat and don't _really_ pass the crs back + if (cb) { + cb(n, NULL); + } + } + + if(!GPS_Packet_Read(fd, &recpkt)) + return gps_errno; + if(!GPS_Send_Ack(fd, &trapkt, &recpkt)) + return gps_errno; + if(recpkt->type != LINK_ID[gps_link_type].Pid_Xfer_Cmplt) { + GPS_Error("A1006_Get: Error transferring courses"); + return FRAMING_ERROR; + } + + if(i != n) { + GPS_Error("A1006_GET: Course entry number mismatch"); + return FRAMING_ERROR; + } + + GPS_Packet_Del(&trapkt); + GPS_Packet_Del(&recpkt); + + if (!GPS_Device_Off(fd)) + return gps_errno; + return n; +} + + +/* @func GPS_A1006_Send ************************************************** +** Send Courses to GPS. +** +** Note that different to other GPS_Axxx_Send functions, the device +** communication is not initialized/ended within the function, since +** this packet transfer is only part of a series of transfers to the +** device. Communication init/end has to be handled by the caller. +** +** @param [r] port [const char *] serial port +** @param [r] crs [GPS_PCourse *] pointer to Course array +** @param [r] n_wkt [int32] number of Course entries +** @param [r] fd [gpsdevh *] pointer to the communication port +** +** @return [int32] success +************************************************************************/ +int32 GPS_A1006_Send(const char *port, + GPS_PCourse *crs, + int32 n_crs, + gpsdevh *fd) +{ + UC data[GPS_ARB_LEN]; + GPS_PPacket tra; + GPS_PPacket rec; + int32 i; + int32 len; + + if(!(tra = GPS_Packet_New()) || !(rec = GPS_Packet_New())) + return MEMORY_ERROR; + + GPS_Util_Put_Short(data,(US) n_crs); + GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Records, + data,2); + if(!GPS_Write_Packet(fd,tra)) + return gps_errno; + if(!GPS_Get_Ack(fd, &tra, &rec)) + { + GPS_Error("A1006_Send: Course start data not acknowledged"); + return FRAMING_ERROR; + } + + for(i=0;iindex = GPS_Util_Get_Short(p); + p+=sizeof(uint16); + p+=sizeof(uint16); // unused + for(i=0;i<16;++i) + (*crs)->course_name[i] = *p++; + (*crs)->track_index = GPS_Util_Get_Short(p); + p+=sizeof(uint16); +} + + +/* @funcstatic GPS_D1006_Send ******************************************* +** +** Form course data string +** +** @param [w] data [UC *] string to write to +** @param [r] crs [GPS_PCourse] course data +** @param [w] len [int32 *] packet length +** +** @return [void] +************************************************************************/ +void GPS_D1006_Send(UC *data, GPS_PCourse crs, int32 *len) +{ + UC *p; + int j; + p = data; + + GPS_Util_Put_Short(p, (US) crs->index); + p += 2; + + GPS_Util_Put_Uint(p,0); + p+=sizeof(uint16); + + for(j=0;j<16;++j) *p++ = crs->course_name[j]; + + GPS_Util_Put_Short(p, (US) crs->track_index); + p += 2; + + *len = p-data; + + return; +} + + +/* @func GPS_A1007_Get ****************************************************** +** +** Get course lap data from GPS +** +** @param [r] port [const char *] serial port +** @param [w] clp [GPS_PCourse_Lap **] course lap array +** +** @return [int32] number of lap entries +************************************************************************/ + +int32 GPS_A1007_Get(const char *port, GPS_PCourse_Lap **clp, pcb_fn cb) +{ + static UC data[2]; + gpsdevh *fd; + GPS_PPacket trapkt; + GPS_PPacket recpkt; + int32 i, n; + + if (gps_course_lap_transfer == -1) + return GPS_UNSUPPORTED; + + if (!GPS_Device_On(port, &fd)) + return gps_errno; + + if (!(trapkt = GPS_Packet_New() ) || !(recpkt = GPS_Packet_New())) + return MEMORY_ERROR; + + GPS_Util_Put_Short(data, + COMMAND_ID[gps_device_command].Cmnd_Transfer_Course_Laps); + GPS_Make_Packet(&trapkt, LINK_ID[gps_link_type].Pid_Command_Data, + data,2); + if(!GPS_Write_Packet(fd,trapkt)) + return gps_errno; + if(!GPS_Get_Ack(fd, &trapkt, &recpkt)) + return gps_errno; + if(!GPS_Packet_Read(fd, &recpkt)) + return gps_errno; + if(!GPS_Send_Ack(fd, &trapkt, &recpkt)) + return gps_errno; + + n = GPS_Util_Get_Short(recpkt->data); + + + if(n) + if(!((*clp)=(GPS_PCourse_Lap *)malloc(n*sizeof(GPS_PCourse_Lap)))) + { + GPS_Error("A1007_Get: Insufficient memory"); + return MEMORY_ERROR; + } + + for(i=0;idata); + break; + default: + GPS_Error("A1007_Get: Unknown Course Lap protocol %d\n", + gps_course_lap_type); + return PROTOCOL_ERROR; + } + + /* Cheat and don't _really_ pass the trkpt back */ + if (cb) { + cb(n, NULL); + } + } + + if(!GPS_Packet_Read(fd, &recpkt)) + return gps_errno; + if(!GPS_Send_Ack(fd, &trapkt, &recpkt)) + return gps_errno; + if(recpkt->type != LINK_ID[gps_link_type].Pid_Xfer_Cmplt) { + GPS_Error("A1007_Get: Error transferring course laps"); + return FRAMING_ERROR; + } + + if(i != n) { + GPS_Error("A1007_GET: Course Lap entry number mismatch"); + return FRAMING_ERROR; + } + + GPS_Packet_Del(&trapkt); + GPS_Packet_Del(&recpkt); + + if (!GPS_Device_Off(fd)) + return gps_errno; + return n; +} + + +/* @func GPS_A1007_Send ************************************************** +** Send Course Lap to GPS. +** +** Note that different to other GPS_Axxx_Send functions, the device +** communication is not initialized/ended within the function, since +** this packet transfer is only part of a series of transfers to the +** device. Communication init/end has to be handled by the caller. +** +** @param [r] port [const char *] serial port +** @param [r] clp [GPS_PCourse_Lap *] pointer to CourseLap array +** @param [r] n_clp [int32] number of CourseLap entries +** @param [r] fd [gpsdevh *] pointer to the communication port +** +** @return [int32] success +************************************************************************/ +int32 GPS_A1007_Send(const char *port, + GPS_PCourse_Lap *clp, + int32 n_clp, + gpsdevh *fd) +{ + UC data[GPS_ARB_LEN]; + GPS_PPacket tra; + GPS_PPacket rec; + int32 i; + int32 len; + + if(!(tra = GPS_Packet_New()) || !(rec = GPS_Packet_New())) + return MEMORY_ERROR; + + GPS_Util_Put_Short(data,(US) n_clp); + GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Records, + data,2); + if(!GPS_Write_Packet(fd,tra)) + return gps_errno; + if(!GPS_Get_Ack(fd, &tra, &rec)) + { + GPS_Error("A1007_Send: CourseLap start data not acknowledged"); + return FRAMING_ERROR; + } + + for(i=0;icourse_index = GPS_Util_Get_Short(p); + p+=sizeof(uint16); + + (*clp)->lap_index = GPS_Util_Get_Short(p); + p+=sizeof(uint16); + + (*clp)->total_time = GPS_Util_Get_Int(p); + p+=sizeof(uint32); + + (*clp)->total_dist = GPS_Util_Get_Float(p); + p+=sizeof(float); + + (*clp)->begin_lat = GPS_Math_Semi_To_Deg(GPS_Util_Get_Int(p)); + p+=sizeof(int32); + (*clp)->begin_lon = GPS_Math_Semi_To_Deg(GPS_Util_Get_Int(p)); + p+=sizeof(int32); + (*clp)->end_lat = GPS_Math_Semi_To_Deg(GPS_Util_Get_Int(p)); + p+=sizeof(int32); + (*clp)->end_lon = GPS_Math_Semi_To_Deg(GPS_Util_Get_Int(p)); + p+=sizeof(int32); + + (*clp)->avg_heart_rate = *p++; + (*clp)->max_heart_rate = *p++; + (*clp)->intensity = *p++; + (*clp)->avg_cadence = *p++; + + return; +} + + +/* @funcstatic GPS_D1007_Send ******************************************* +** +** Form course lap data string +** +** @param [w] data [UC *] string to write to +** @param [r] clp [GPS_PCourse_Lap] course lap data +** @param [w] len [int32 *] packet length +** +** @return [void] +************************************************************************/ +void GPS_D1007_Send(UC *data, GPS_PCourse_Lap clp, int32 *len) +{ + UC *p; + p = data; + + GPS_Util_Put_Short(p, (US) clp->course_index); + p += 2; + + GPS_Util_Put_Short(p, (US) clp->lap_index); + p += 2; + + GPS_Util_Put_Uint(p, clp->total_time); + p+=sizeof(int32); + + GPS_Util_Put_Float(p,clp->total_dist); + p+= sizeof(float); + + GPS_Util_Put_Int(p,GPS_Math_Deg_To_Semi(clp->begin_lat)); + p+=sizeof(int32); + GPS_Util_Put_Int(p,GPS_Math_Deg_To_Semi(clp->begin_lon)); + p+=sizeof(int32); + + GPS_Util_Put_Int(p,GPS_Math_Deg_To_Semi(clp->end_lat)); + p+=sizeof(int32); + GPS_Util_Put_Int(p,GPS_Math_Deg_To_Semi(clp->end_lon)); + p+=sizeof(int32); + + *p++ = clp->avg_heart_rate; + + *p++ = clp->max_heart_rate; + + *p++ = clp->intensity; + + *p++ = clp->avg_cadence; + + *len = p-data; + + return; +} + + +/* @func GPS_A1008_Get ****************************************************** +** +** Get course points from GPS +** +** @param [r] port [const char *] serial port +** @param [w] cpt [GPS_PCourse_Point **] course point array +** +** @return [int32] number of course point entries +************************************************************************/ + +int32 GPS_A1008_Get(const char *port, GPS_PCourse_Point **cpt, pcb_fn cb) +{ + static UC data[2]; + gpsdevh *fd; + GPS_PPacket trapkt; + GPS_PPacket recpkt; + int32 i, n; + + if (gps_course_point_transfer == -1) + return GPS_UNSUPPORTED; + + if (!GPS_Device_On(port, &fd)) + return gps_errno; + + if (!(trapkt = GPS_Packet_New() ) || !(recpkt = GPS_Packet_New())) + return MEMORY_ERROR; + + GPS_Util_Put_Short(data, + COMMAND_ID[gps_device_command].Cmnd_Transfer_Course_Points); + GPS_Make_Packet(&trapkt, LINK_ID[gps_link_type].Pid_Command_Data, + data,2); + if(!GPS_Write_Packet(fd,trapkt)) + return gps_errno; + if(!GPS_Get_Ack(fd, &trapkt, &recpkt)) + return gps_errno; + if(!GPS_Packet_Read(fd, &recpkt)) + return gps_errno; + if(!GPS_Send_Ack(fd, &trapkt, &recpkt)) + return gps_errno; + + n = GPS_Util_Get_Short(recpkt->data); + + + if(n) + if(!((*cpt)=(GPS_PCourse_Point *)malloc(n*sizeof(GPS_PCourse_Point)))) + { + GPS_Error("A1008_Get: Insufficient memory"); + return MEMORY_ERROR; + } + + for(i=0;idata); + break; + default: + GPS_Error("A1008_Get: Unknown Course Point protocol %d\n", + gps_course_point_type); + return PROTOCOL_ERROR; + } + + /* Cheat and don't _really_ pass the trkpt back */ + if (cb) { + cb(n, NULL); + } + } + + if(!GPS_Packet_Read(fd, &recpkt)) + return gps_errno; + if(!GPS_Send_Ack(fd, &trapkt, &recpkt)) + return gps_errno; + if(recpkt->type != LINK_ID[gps_link_type].Pid_Xfer_Cmplt) { + GPS_Error("A1008_Get: Error transferring course points"); + return FRAMING_ERROR; + } + + if(i != n) { + GPS_Error("A1008_GET: Course Point entry number mismatch"); + return FRAMING_ERROR; + } + + GPS_Packet_Del(&trapkt); + GPS_Packet_Del(&recpkt); + + if (!GPS_Device_Off(fd)) + return gps_errno; + return n; +} + + +/* @func GPS_A1008_Send ************************************************** +** Send Course Points to GPS. +** +** Note that different to other GPS_Axxx_Send functions, the device +** communication is not initialized/ended within the function, since +** this packet transfer is only part of a series of transfers to the +** device. Communication init/end has to be handled by the caller. +** +** +** @param [r] port [const char *] serial port +** @param [r] cpt [GPS_PCourse_Point *] pointer to CoursePoint array +** @param [r] n_cpt [int32] number of CoursePoint entries +** @param [r] fd [gpsdevh *] pointer to the communication port +** +** @return [int32] success +************************************************************************/ +int32 GPS_A1008_Send(const char *port, + GPS_PCourse_Point *cpt, + int32 n_cpt, + gpsdevh *fd) +{ + UC data[GPS_ARB_LEN]; + GPS_PPacket tra; + GPS_PPacket rec; + int32 i; + int32 len; + + if(!(tra = GPS_Packet_New()) || !(rec = GPS_Packet_New())) + return MEMORY_ERROR; + + GPS_Util_Put_Short(data,(US) n_cpt); + GPS_Make_Packet(&tra, LINK_ID[gps_link_type].Pid_Records, + data,2); + if(!GPS_Write_Packet(fd,tra)) + return gps_errno; + if(!GPS_Get_Ack(fd, &tra, &rec)) + { + GPS_Error("GPS_A1008_Send: Coursepoint start data not acknowledged"); + return FRAMING_ERROR; + } + + for(i=0;iname[i] = *p++; + p++; //unused + (*cpt)->course_index = GPS_Util_Get_Short(p); + p+=sizeof(uint16); + p+=sizeof(uint16); // unused + + t = GPS_Util_Get_Uint(p); + (*cpt)->track_point_time = GPS_Math_Gtime_To_Utime((time_t)t); + p+=sizeof(uint32); + + (*cpt)->point_type = *p++; + +} + + +/* @funcstatic GPS_D1012_Send ******************************************* +** +** Form course point data string +** +** @param [w] data [UC *] string to write to +** @param [r] cpt [GPS_PCourse_Point] course point data +** @param [w] len [int32 *] packet length +** +** @return [void] +************************************************************************/ +void GPS_D1012_Send(UC *data, GPS_PCourse_Point cpt, int32 *len) +{ + UC *p; + int j; + p = data; + + for(j=0;j<11;++j) *p++ = cpt->name[j]; + + GPS_Util_Put_Uint(p,0); + p++; + + GPS_Util_Put_Short(p, (US) cpt->course_index); + p += 2; + + GPS_Util_Put_Uint(p,0); + p+=sizeof(uint16); + + GPS_Util_Put_Uint(p,GPS_Math_Utime_To_Gtime(cpt->track_point_time)); + p+=sizeof(uint32); + + *p++ = cpt->point_type; + + *len = p-data; + + return; +} + + +/* + * It's unfortunate that these aren't constant and therefore switchable, + * but they really are runtime variable. Sigh. + */ +const char * +Get_Pkt_Type(US p, US d0, const char **xinfo) +{ + *xinfo = NULL; +#define LT LINK_ID[gps_link_type] + if (p == LT.Pid_Ack_Byte) + return "ACK"; + if (p == LT.Pid_Command_Data) { + switch (d0) { + case 0: *xinfo = "Abort"; break; + case 1: *xinfo = "Xfer Alm"; break; + case 2: *xinfo = "Xfer Posn"; break; + case 3: *xinfo = "Xfer Prx"; break; + case 4: *xinfo = "Xfer Rte"; break; + case 5: *xinfo = "Xfer Time"; break; + case 6: *xinfo = "Xfer Trk"; break; + case 7: *xinfo = "Xfer Wpt"; break; + case 8: *xinfo = "Power Down"; break; + case 49: *xinfo = "Xfer PVT Start"; break; + case 50: *xinfo = "Xfer PVT Stop"; break; + case 92: *xinfo = "Flight Records"; break; + case 117: *xinfo = "Xfer Laps"; break; + case 121: *xinfo = "Xfer Categories"; break; + case 450: *xinfo = "Xfer Runs"; break; + case 451: *xinfo = "Xfer Workouts"; break; + case 452: *xinfo = "Xfer Wkt Occurrences"; break; + case 453: *xinfo = "Xfer User Profile "; break; + case 454: *xinfo = "Xfer Wkt Limits"; break; + case 561: *xinfo = "Xfer Courses"; break; + case 562: *xinfo = "Xfer Course Laps"; break; + case 563: *xinfo = "Xfer Course Point"; break; + case 564: *xinfo = "Xfer Course Tracks"; break; + case 565: *xinfo = "Xfer Course Limits"; break; + + default: *xinfo = "Unknown"; + } + return "CMDDAT"; + } + if (p == LT.Pid_Protocol_Array) + return "PRTARR"; + if (p == LT.Pid_Product_Rqst) + return "PRDREQ"; + if (p == LT.Pid_Product_Data) + return "PRDDAT"; + if (p == LT.Pid_Ext_Product_Data) + return "PRDEDA"; + + if (p == LT.Pid_Xfer_Cmplt) + return "XFRCMP"; + if (p == LT.Pid_Date_Time_Data) + return "DATTIM"; + if (p == LT.Pid_Position_Data) + return "POS"; + if (p == LT.Pid_Prx_Wpt_Data) + return "WPT"; + if (p == LT.Pid_Nak_Byte) + return "NAK"; + if (p == LT.Pid_Records) + return "RECORD"; + if (p == LT.Pid_Rte_Hdr) + return "RTEHDR"; + if (p == LT.Pid_Rte_Wpt_Data) + return "RTEWPT"; + if (p == LT.Pid_Almanac_Data) + return "RALMAN"; + if (p == LT.Pid_Trk_Data) + return "TRKDAT"; + if (p == LT.Pid_Wpt_Data) + return "WPTDAT"; + if (p == LT.Pid_Pvt_Data) + return "PVTDAT"; + if (p == LT.Pid_Rte_Link_Data) + return "LNKDAT"; + if (p == LT.Pid_Trk_Hdr) + return "TRKHDR"; + + if (p == LT.Pid_FlightBook_Record) + return "FLIBOO"; + if (p == LT.Pid_Lap) + return "LAPDAT"; + if (p == LT.Pid_Wpt_Cat) + return "WPTCAT"; + if (p == LT.Pid_Run) + return "RUNDAT"; + if (p == LT.Pid_Workout) + return "WKTDAT"; + if (p == LT.Pid_Workout_Occurrence) + return "WKTOCC"; + if (p == LT.Pid_Fitness_User_Profile) + return "UPROFI"; + if (p == LT.Pid_Workout_Limits) + return "WKTLIM"; + if (p == LT.Pid_Course) + return "CRSDAT"; + if (p == LT.Pid_Course_Lap) + return "CRSLAP"; + if (p == LT.Pid_Course_Point) + return "CRSPOI"; + if (p == LT.Pid_Course_Trk_Hdr) + return "CRSTHD"; + if (p == LT.Pid_Course_Trk_Data) + return "CRSTDA"; + if (p == LT.Pid_Course_Limits) + return "CRSLIM"; + if (p == LT.Pid_Trk2_Hdr) + return "TRKHD2"; + if (p == GUSB_REQUEST_BULK) return "REQBLK"; if (p == GUSB_SESSION_START) @@ -6424,3 +7502,26 @@ Get_Pkt_Type(US p, US d0, const char **xinfo) return "UNKNOWN"; } + + +/* @funcstatic Is_Trackpoint_Invalid *********************************** +** +** Check if a trackpoint is invalid. Needed for D303/D304 to check for +** pauses. +** +** +** @param [r] trk [GPS_PTrack *] track +** @param [r] n [int32] Index of trackpoint +** +** @return [UC] 1 if the trackpoint is invalid +************************************************************************/ +static UC Is_Trackpoint_Invalid(GPS_PTrack *trk) +{ + /* FIXME: We should have more *_is_unknown fields instead of + * checking for special values here (e.g. cadence = 0 would be + * perfectly valid, but GPS_D303b_Get() chose to use it to mark + * nonexistent cadence data. + */ + return (*trk)->no_latlon && (*trk)->distance > 1e24 && + !(*trk)->heartrate && !(*trk)->cadence; +} diff --git a/jeeps/gpsapp.h b/jeeps/gpsapp.h index 04ed23528..300bddf3f 100644 --- a/jeeps/gpsapp.h +++ b/jeeps/gpsapp.h @@ -22,8 +22,11 @@ int32 GPS_A201_Send(const char *port, GPS_PWay *way, int32 n); int32 GPS_A300_Get(const char *port, GPS_PTrack **trk, pcb_fn cb); int32 GPS_A301_Get(const char *port, GPS_PTrack **trk, pcb_fn cb); +int32 GPS_A302_Get(const char *port, GPS_PTrack **trk, pcb_fn cb); int32 GPS_A300_Send(const char *port, GPS_PTrack *trk, int32 n); -int32 GPS_A301_Send(const char *port, GPS_PTrack *trk, int32 n); /*A302*/ +int32 GPS_A301_Send(const char *port, GPS_PTrack *trk, int32 n); +int32 GPS_A302_Send(const char *port, GPS_PTrack *trk, int32 n, + gpsdevh *fd); int32 GPS_D300_Get(GPS_PTrack *trk, int32 entries, gpsdevh *h); void GPS_D300b_Get(GPS_PTrack *trk, UC *data); @@ -32,9 +35,9 @@ void GPS_D302b_Get(GPS_PTrack *trk, UC *data); void GPS_D303b_Get(GPS_PTrack *trk, UC *data); /*D304*/ void GPS_D310_Get(GPS_PTrack *trk, UC *s); void GPS_D311_Get(GPS_PTrack *trk, UC *s); -void GPS_D300_Send(UC *data, GPS_PTrack trk); -void GPS_D301_Send(UC *data, GPS_PTrack trk, int type); -void GPS_D304_Send(UC *data, GPS_PTrack trk); +void GPS_D300_Send(UC *data, GPS_PTrack trk, int32 *len); +void GPS_D301_Send(UC *data, GPS_PTrack trk, int32 *len, int type); +void GPS_D303_Send(UC *data, GPS_PTrack trk, int32 *len, int protoid); void GPS_D310_Send(UC *data, GPS_PTrack trk, int32 *len); void GPS_D311_Send(UC *data, GPS_PTrack trk, int32 *len); @@ -59,28 +62,42 @@ int32 GPS_A800_Off(const char *port, gpsdevh **fd); int32 GPS_A800_Get(gpsdevh **fd, GPS_PPvt_Data *packet); void GPS_D800_Get(GPS_PPacket packet, GPS_PPvt_Data *pvt); -int32 GPS_A906_Get(const char *port, GPS_PLap **lap, pcb_fn cb); -void GPS_D1011b_Get(GPS_PLap *Lap,UC *data); /*D906 D1001 D1015*/ +int32 GPS_A906_Get(const char *port, GPS_PLap **lap, pcb_fn cb); +void GPS_D1011b_Get(GPS_PLap *Lap,UC *data); /*D906 D1001 D1015*/ + +int32 GPS_A1006_Get(const char *port, GPS_PCourse **crs, pcb_fn cb); +int32 GPS_A1006_Send(const char *port, GPS_PCourse *crs, int32 n_crs, + gpsdevh *fd); +void GPS_D1006_Get(GPS_PCourse *crs, UC *p); +void GPS_D1006_Send(UC *data, GPS_PCourse crs, int32 *len); + +int32 GPS_A1007_Get(const char *port, GPS_PCourse_Lap **clp, pcb_fn cb); +int32 GPS_A1007_Send(const char *port, GPS_PCourse_Lap *clp, int32 n_clp, + gpsdevh *fd); +void GPS_D1007_Get(GPS_PCourse_Lap *clp, UC *p); +void GPS_D1007_Send(UC *data, GPS_PCourse_Lap clp, int32 *len); + +int32 GPS_A1008_Get(const char *port, GPS_PCourse_Point **cpt, pcb_fn cb); +int32 GPS_A1008_Send(const char *port, GPS_PCourse_Point *cpt, int32 n_cpt, + gpsdevh *fd); +void GPS_D1012_Get(GPS_PCourse_Point *cpt, UC *p); +void GPS_D1012_Send(UC *data, GPS_PCourse_Point cpt, int32 *len); /* Unhandled documented protocols, as of: Garmin Device Interface Specification, May 19, 2006, Drawing Number: 001-00063-00 Rev. C -A650 – FlightBook Transfer Protocol -A1000 – Run Transfer Protocol +A650 FlightBook Transfer Protocol +A1000 Run Transfer Protocol Capability A1000: D1009 D1000 D1010 -A1002 – Workout Transfer Protocol +A1002 Workout Transfer Protocol Capability A1002: D1008 D1002 Capability A1003: D1003 -A1004 – Fitness User Profile Transfer Protocol +A1004 Fitness User Profile Transfer Protocol Capability A1004: D1004 -A1005 – Workout Limits Transfer Protocol +A1005 Workout Limits Transfer Protocol Capability A1005: D1005 -A1006 – Course Transfer Protocol - Capability A1006: D1006 - Capability A1007: D1007 - Capability A1008: D1012 -A1009 – Course Limits Transfer Protocol +A1009 Course Limits Transfer Protocol Capability A1009: D1013 */ /* Unimplemted and Undocumented, as listed from the following device/sw: diff --git a/jeeps/gpscom.c b/jeeps/gpscom.c index 883593ff3..302c39d70 100644 --- a/jeeps/gpscom.c +++ b/jeeps/gpscom.c @@ -5,6 +5,8 @@ ** @version 1.0 ** @modified Dec 28 1999 Alan Bleasby. First version ** @modified Copyright (C) 2005, 2006 Robert Lipe +** @modified Copyright (C) 2007 Achim Schumacher +** @modified Copyright (C) 2010 Martin Buck ** @@ ** ** This library is free software; you can redistribute it and/or @@ -269,9 +271,14 @@ int32 GPS_Command_Send_Track(const char *port, GPS_PTrack *trk, int32 n) ret = GPS_A300_Send(port, trk, n); break; case pA301: - case pA302: ret = GPS_A301_Send(port, trk, n); break; + case pA302: + /* Units with A302 don't support track upload, so we convert the + * track to a course on the fly and send that instead + */ + ret = GPS_Command_Send_Track_As_Course(port, trk, n); + break; default: GPS_Error("Send_Track: Unknown track protocol %d.", gps_trk_transfer); break; @@ -672,20 +679,464 @@ int32 GPS_Command_Get_Lap(const char *port, GPS_PLap **lap, pcb_fn cb) return ret; } + +/* @func GPS_Command_Get_Course *************************************** +** +** Get Courses from GPS. According to Garmin protocol specification, this +** includes getting all course laps, course tracks and course points +** from the device. +** +** @param [r] port [const char *] serial port +** @param [w] crs [GPS_PCourse **] pointer to course array +** @param [w] clp [GPS_PCourse_Lap **] pointer to course lap array +** @param [w] trk [GPS_PTrack **] pointer to track array +** @param [w] cpt [GPS_PCourse_Point **] pointer to course point array +** @param [w] n_clp [int32 **] pointer to number of lap entries +** @param [w] n_trk [int32 **] pointer to number of track entries +** @param [w] n_cpt [int32 **] pointer to number of course point entries +** +** @return [int32] number of course entries +************************************************************************/ +int32 GPS_Command_Get_Course + (const char *port, + GPS_PCourse **crs, + GPS_PCourse_Lap **clp, + GPS_PTrack **trk, + GPS_PCourse_Point **cpt, + int32 *n_clp, + int32 *n_trk, + int32 *n_cpt, + pcb_fn cb) +{ + int32 ret=0; + + if(gps_course_transfer == -1) + return GPS_UNSUPPORTED; + + switch(gps_course_transfer) + { + case pA1006: + ret = GPS_A1006_Get(port,crs,cb); + break; + default: + GPS_Error("Get_Course: Unknown course protocol"); + return PROTOCOL_ERROR; + } + + switch(gps_course_lap_transfer) + { + case pA1007: + *n_clp = GPS_A1007_Get(port,clp, 0); + break; + default: + GPS_Error("Get_Course: Unknown course lap protocol"); + return PROTOCOL_ERROR; + } + + switch(gps_course_trk_transfer) + { + case pA1012: + GPS_Error("Get_Course: Not implemented track protocol %d\n", + gps_trk_transfer); + break; + case pA302: + *n_trk = GPS_A302_Get(port,trk,cb); + break; + default: + GPS_Error("Get_Course: Unknown course track protocol %d\n", + gps_trk_transfer); + return PROTOCOL_ERROR; + } + + switch(gps_course_point_transfer) + { + case pA1008: + *n_cpt = GPS_A1008_Get(port,cpt, 0); + break; + default: + GPS_Error("Get_Course: Unknown course point protocol"); + return PROTOCOL_ERROR; + } + + return ret; +} + + +/* @func GPS_Command_Send_Course *************************************** +** +** Send Courses to GPS. According to Garmin protocol specification, this +** includes sending all course laps, course tracks and course points +** to the device. +** +** Data integrity is being checked: only those course laps, tracks and +** points are sent to the device which belong to a course in the course +** array. Otherwise, those data will be silently dropped. +** +** @param [r] port [const char *] serial port +** @param [w] crs [GPS_PCourse **] course array +** @param [w] clp [GPS_PCourse_Lap *] course lap array +** @param [w] trk [GPS_PTrack *] track array +** @param [w] cpt [GPS_PCourse_Point *] course point array +** @param [w] n_crs [int32] number of course entries +** @param [w] n_clp [int32] number of lap entries +** @param [w] n_trk [int32] number of track entries +** @param [w] n_cpt [int32] number of course point entries +** +** @return [int32] Success +************************************************************************/ +int32 GPS_Command_Send_Course + (const char *port, + GPS_PCourse *crs, + GPS_PCourse_Lap *clp, + GPS_PTrack *trk, + GPS_PCourse_Point *cpt, + int32 n_crs, + int32 n_clp, + int32 n_trk, + int32 n_cpt) +{ + gpsdevh *fd; + int32 ret_crs=0; + int32 ret_clp=0; + int32 ret_trk=0; + int32 ret_cpt=0; + + if(gps_course_transfer == -1) + return GPS_UNSUPPORTED; + + /* Initialize device communication: + * In contrast to other transfer protocols, this has to be handled here; + * shutting off communication in between the different parts + * could lead to data corruption on the device because all the courses + * and their associated lap and track data have to be sent in one + * transaction. + */ + if(!GPS_Device_On(port,&fd)) + return gps_errno; + + switch(gps_course_transfer) + { + case pA1006: + ret_crs = GPS_A1006_Send(port,crs,n_crs,fd); + break; + default: + GPS_Error("Send_Course: Unknown course protocol"); + return PROTOCOL_ERROR; + } + + switch(gps_course_lap_transfer) + { + case pA1007: + ret_clp = GPS_A1007_Send(port,clp,n_clp,fd); + break; + default: + GPS_Error("Send_Course: Unknown course lap protocol"); + return PROTOCOL_ERROR; + } + + switch(gps_course_trk_transfer) + { + case pA1012: + GPS_Error("Send_Course: Not implemented track protocol %d\n", + gps_trk_transfer); + break; + case pA302: + ret_trk = GPS_A302_Send(port,trk,n_trk,fd); + break; + default: + GPS_Error("Send_Course: Unknown course track protocol %d\n", + gps_trk_transfer); + return PROTOCOL_ERROR; + } + + switch(gps_course_point_transfer) + { + case pA1008: + ret_cpt = GPS_A1008_Send(port,cpt,n_cpt,fd); + break; + default: + GPS_Error("Send_Course: Unknown course point protocol"); + return PROTOCOL_ERROR; + } + + if(!GPS_Device_Off(fd)) + return gps_errno; + + + return ret_crs * ret_clp * ret_trk * ret_cpt; +} + + +static uint32 Unique_Course_Index(GPS_PCourse *crs, int n_crs) +{ + uint32 idx; + int i; + + for (idx=0; ; idx++) + { + for (i=0; iindex==idx) + break; /* Already have this index */ + if (i>=n_crs) + return idx; /* Found unused index */ + } +} + + +static uint32 Unique_Track_Index(GPS_PCourse *crs, int n_crs) +{ + uint32 idx; + int i; + + for (idx=0; ; idx++) + { + for (i=0; itrack_index==idx) + break; /* Already have this index */ + if (i>=n_crs) + return idx; /* Found unused index */ + } +} + + +static void +Course_Garbage_Collect(GPS_PCourse *crs, int *n_crs, + GPS_PCourse_Lap *clp, int *n_clp, + GPS_PTrack *ctk, int *n_ctk, + GPS_PCourse_Point *cpt, int *n_cpt) { + int i, j; + + /* Remove courses with duplicate names, keeping newest */ +restart_courses: + for (i=*n_crs-1; i>0; i--) + { + for (j=i-1; j>=0; j--) + { + if (!strcmp(crs[i]->course_name, crs[j]->course_name)) + { + /* Remove course */ + GPS_Course_Del(&crs[j]); + memmove(&crs[j], &crs[j+1], (*n_crs-j-1)*sizeof(*crs)); + (*n_crs)--; + goto restart_courses; + } + } + } + + /* Remove unreferenced laps */ +restart_laps: + for (i=0; i<*n_clp; i++) + { + for (j=0; j<*n_crs; j++) + if (crs[j]->index == clp[i]->course_index) + break; + if (j>=*n_crs) + { + /* Remove lap */ + GPS_Course_Lap_Del(&clp[i]); + memmove(&clp[i], &clp[i+1], (*n_clp-i-1)*sizeof(*clp)); + (*n_clp)--; + goto restart_laps; + } + } + + /* Remove unreferenced tracks */ +restart_tracks: + for (i=0; i<*n_ctk; i++) + { + uint32 trk_idx; + + if (!ctk[i]->ishdr) + continue; + trk_idx = strtoul(ctk[i]->trk_ident, NULL, 0); + for (j=0; j<*n_crs; j++) + if (crs[j]->index == trk_idx) + break; + if (j>=*n_crs) + { + /* Remove track */ + for (j=i; j<*n_ctk; j++) + { + if (j!=i && ctk[j]->ishdr) + break; + GPS_Track_Del(&ctk[j]); + } + memmove(&ctk[i], &ctk[j], (*n_ctk-j)*sizeof(*ctk)); + *(n_ctk) -= j-i; + goto restart_tracks; + } + } + + /* Remove unreferenced course points */ +restart_course_points: + for (i=0; i<*n_cpt; i++) + { + for (j=0; j<*n_crs; j++) + if (crs[j]->index == cpt[i]->course_index) + break; + if (j>=*n_crs) + { + /* Remove course point */ + GPS_Course_Point_Del(&cpt[i]); + memmove(&cpt[i], &cpt[i+1], (*n_cpt-i-1)*sizeof(*cpt)); + (*n_cpt)--; + goto restart_course_points; + } + } +} + + +/* @func GPS_Command_Send_Track_As_Course ****************************** +** +** Convert track log to course, then send to GPS. Since sending a course +** to the device will erase all existing courses regardless of their +** name or index, we first have to download all courses, merge the new +** one and then send all courses at once. +** +** @param [r] port [const char *] serial port +** @param [r] trk [GPS_PTrack *] track array +** @param [r] n [int32] number of track entries +** +** @return [int32] success +************************************************************************/ + +int32 GPS_Command_Send_Track_As_Course(const char *port, GPS_PTrack *trk, int32 n) +{ + GPS_PCourse *crs = NULL; + GPS_PCourse_Lap *clp = NULL; + GPS_PTrack *ctk = NULL; + GPS_PCourse_Point *cpt = NULL; + int n_crs, n_clp=0, n_ctk=0, n_cpt=0; + int i, trk_end, trk_crs; + int32 ret; + + /* Read existing courses from device */ + n_crs = GPS_Command_Get_Course(port, &crs, &clp, &ctk, &cpt, &n_clp, &n_ctk, &n_cpt, NULL); + if (n_crs < 0) return n_crs; + + /* Create new course+lap+track points for each track */ + trk_crs = n_crs; + for (i=0;iishdr) + continue; + + /* Find end of track */ + for (trk_end=i; trk_endishdr) + break; + if (trk_end==i) + trk_end=0; /* Empty track */ + + /* Create & append course */ + crs = realloc(crs, (n_crs+1) * sizeof(GPS_PCourse)); + if (!crs) return MEMORY_ERROR; + crs[n_crs] = GPS_Course_New(); + if (!crs[n_crs]) return MEMORY_ERROR; + + crs[n_crs]->index = Unique_Course_Index(crs, n_crs); + strncpy(crs[n_crs]->course_name, trk[i]->trk_ident, + sizeof(crs[n_crs]->course_name)-1); + + crs[n_crs]->track_index = Unique_Track_Index(crs, n_crs); + + /* Create & append new lap */ + clp = realloc(clp, (n_clp+1) * sizeof(GPS_PCourse_Lap)); + if (!clp) return MEMORY_ERROR; + clp[n_clp] = GPS_Course_Lap_New(); + if (!clp[n_clp]) return MEMORY_ERROR; + + clp[n_clp]->course_index = crs[n_crs]->index; /* Index of associated course */ + clp[n_clp]->lap_index = 0; /* Lap index, unique per course */ + clp[n_clp]->total_time = 0; /* FIXME: Calculate */ + clp[n_clp]->total_dist = 0; /* FIXME: Calculate */ + if (trk_end) + { + clp[n_clp]->begin_lat = trk[i+1]->lat; + clp[n_clp]->begin_lon = trk[i+1]->lon; + clp[n_clp]->end_lat = trk[trk_end]->lat; + clp[n_clp]->end_lon = trk[trk_end]->lon; + } + else + { + clp[n_clp]->begin_lat = 0x7fffffff; + clp[n_clp]->begin_lon = 0x7fffffff; + clp[n_clp]->end_lat = 0x7fffffff; + clp[n_clp]->end_lon = 0x7fffffff; + } + clp[n_clp]->avg_heart_rate = 0; /* FIXME: Calculate */ + clp[n_clp]->max_heart_rate = 0; /* FIXME: Calculate */ + clp[n_clp]->intensity = 0; + clp[n_clp]->avg_cadence = 0xff; /* FIXME: Calculate */ + n_crs++; + n_clp++; + } + + /* Append new track points */ + ctk = realloc(ctk, (n_ctk+n) * sizeof(GPS_PTrack)); + if (!ctk) return MEMORY_ERROR; + + for (i=0;iishdr) + { + /* Index of new track, must match the track index in associated course */ + memset(ctk[n_ctk]->trk_ident, 0, sizeof(ctk[n_ctk]->trk_ident)); + sprintf(ctk[n_ctk]->trk_ident, "%d", crs[trk_crs]->track_index); + trk_crs++; + } + n_ctk++; + } + + /* Remove course data that's no longer needed */ + Course_Garbage_Collect(crs, &n_crs, clp, &n_clp, ctk, &n_ctk, cpt, &n_cpt); + + /* Finally send courses including new ones to device */ + ret = GPS_Command_Send_Course(port, crs, clp, ctk, cpt, + n_crs, n_clp, n_ctk, n_cpt); + + for (i=0;i